package com.penglecode.spring3.jdbc;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

public class AutoBoxingRowMapper<T> implements RowMapper<T> {
	
	private static final Logger logger = LoggerFactory.getLogger(AutoBoxingRowMapper.class);
	
	public static final Map<Class<?>, Map<String,PropertyDescriptor>> globalBeanPropertyMapCache = new ConcurrentHashMap<Class<?>, Map<String,PropertyDescriptor>>();
	
	public static final String ONE_TO_ONE_EXPRESSION = "\\.";
	
	public static final Pattern ONE_TO_ONE_EXPRESSION_PATTERN = Pattern.compile("\\w+\\.\\w+");
	
	private Class<T> beanClass;
	
	private String ignoredCharsInColumnNameRegex = "_";
	
	private boolean nullStringToEmptyString = false;
	
	private Map<String,Object> tempBeanPropertyValue = new HashMap<String,Object>();
	
	public AutoBoxingRowMapper(Class<T> beanClass){
		super();
		try {
			Assert.notNull(beanClass, "'beanClass' can not be null!");
			this.beanClass = beanClass;
			parseBeanProperty(beanClass);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public T mapRow(ResultSet rs, int rowNum) throws SQLException {
		T newInstance = null;
		try {
			//获取元数据
			ResultSetMetaData rsmd = rs.getMetaData();
			int columnCount = rsmd.getColumnCount();
			PropertyDescriptor propertyDescriptor = null;
			
			tempBeanPropertyValue.clear();//reset
			newInstance = newInstance(beanClass);
			
			for(int i = 1; i <= columnCount; i++){
				try {
					String columnName = getColumnKey(JdbcUtils.lookupColumnName(rsmd, i)); //获取resultset结果集中第i列对应的列名
					propertyDescriptor = getPropertyDescriptor(columnName); //解析当前列名key对应的属性
					if(propertyDescriptor != null){ //调用PropertyDescriptor的WriteMethod方法设置其值
						Object propValue = getColumnValue(rs, i, propertyDescriptor.getPropertyType());//获取resultset结果集中第i列对应的值
						if(ONE_TO_ONE_EXPRESSION_PATTERN.matcher(columnName).matches()){ //one-to-one关系
							String oneToOnePrefixKey = null;
							String[] splits = columnName.split(ONE_TO_ONE_EXPRESSION);
							oneToOnePrefixKey = splits[0];
							PropertyDescriptor oneToOnePrefixPD = getPropertyDescriptor(oneToOnePrefixKey);
							if(oneToOnePrefixPD == null){
								continue;
							}
							Object val = tempBeanPropertyValue.get(oneToOnePrefixKey);
							if(val == null){
								val = newInstance(oneToOnePrefixPD.getPropertyType());
								oneToOnePrefixPD.getWriteMethod().invoke(newInstance, val);
								tempBeanPropertyValue.put(oneToOnePrefixKey, val);
							}
							propertyDescriptor.getWriteMethod().invoke(val, propValue);
						}else{
							propertyDescriptor.getWriteMethod().invoke(newInstance, propValue);
						}
					}
				} catch (Exception e) {
					logger.error(e.getMessage(), e);
					throw new RuntimeException(e.getMessage(), e);
				}
			}
		}catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
		return newInstance;
	}
	
	public <E> E newInstance(Class<E> beanClass) throws Exception {
		try {
			return beanClass.newInstance();
		} catch (Exception e) {
			String msg = String.format("Initializing an example instance of %s using default constructor failed!", beanClass);
			logger.error(msg, e);
			throw e;
		}
	}
	
	public Map<String,PropertyDescriptor> parseBeanProperty(Class<?> beanClass) throws Exception {
		Map<String,PropertyDescriptor> beanPropertyMap = null;
		if((beanPropertyMap = globalBeanPropertyMapCache.get(beanClass)) == null){ //fisrt init
			PropertyDescriptor[] props = Introspector.getBeanInfo(beanClass, Object.class).getPropertyDescriptors();
			if (props != null) {
				beanPropertyMap = new HashMap<String,PropertyDescriptor>();
				for (int i = 0; i < props.length; i++) {
					beanPropertyMap.put(props[i].getName().toLowerCase().trim(), props[i]);
				}
				globalBeanPropertyMapCache.put(beanClass, beanPropertyMap);
			}
		}
		return beanPropertyMap;
	}

	protected PropertyDescriptor getPropertyDescriptor(String columnName) throws Exception {
		if(!StringUtils.hasText(columnName)){
			return null;
		}
		columnName = columnName.toLowerCase().trim();
		Map<String,PropertyDescriptor> rootBeanPropertyMap = globalBeanPropertyMapCache.get(beanClass);
		PropertyDescriptor tempDescriptor = null;
		
		if(ONE_TO_ONE_EXPRESSION_PATTERN.matcher(columnName).matches()){ //one-to-one关系
			String oneToOnePrefixKey = null, oneToOneSuffixKey = null;
			String[] splits = columnName.split(ONE_TO_ONE_EXPRESSION);
			oneToOnePrefixKey = splits[0];
			oneToOneSuffixKey = splits[1];
			tempDescriptor = rootBeanPropertyMap.get(oneToOnePrefixKey);
			Map<String,PropertyDescriptor> beanPropertyMap = globalBeanPropertyMapCache.get(tempDescriptor.getPropertyType());
			if(CollectionUtils.isEmpty(beanPropertyMap)){
				beanPropertyMap = parseBeanProperty(tempDescriptor.getPropertyType()); //初始化one-to-one的后一方BeanPropertyMap
			}
			return beanPropertyMap.get(oneToOneSuffixKey);
		}else{ //普通简单类型值,无对象关系
			return rootBeanPropertyMap.get(columnName);
		}
	}
	
	protected String getColumnKey(String columnName) {
		if(columnName != null){
			columnName = columnName.toLowerCase().trim();
			if(StringUtils.hasText(ignoredCharsInColumnNameRegex)){
				columnName = columnName.replaceAll(ignoredCharsInColumnNameRegex, "");
			}
		}
		return columnName;
	}
	
	protected Object getColumnValue(ResultSet rs, int index, Class<?> requiredType) throws Exception {
		Object val = JdbcUtils.getResultSetValue(rs, index, requiredType);
		if(nullStringToEmptyString){
			if(String.class.equals(requiredType) && val == null){
				val = "";
			}
		}
		return val;
	}

}